package loginserver;

/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Credit goes to l2j-client project <https://code.google.com/p/l2jserver-client/> and l2j project <http://www.l2jserver.com/>
 */

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class LoginServerToClient extends Thread
{
	@Override
	public void run()
	{
		ServerSocket client = null;
		try
		{
			client = new ServerSocket(2106, 50, InetAddress.getByName("127.0.0.1"));
			while (true)
			{
				Socket clientSocket = client.accept();
				System.out.println("LoginServerClient: client connected");
				ServerToClientThread clientThread = new ServerToClientThread(clientSocket);
				clientThread.start();
			}
		}
		catch (Exception e)
		{
			System.out.println("LoginServerToClient: client's accepting: " + e.getLocalizedMessage());
			try
			{
				if (client != null)
					client.close();
			}
			catch (IOException e1)
			{}
		}
	}
}

class ServerToClientThread extends Thread
{
	private static Socket serverSocket;
	private static Socket clientSocket;
	private static DataInputStream fromServer;
	private static DataOutputStream toClient;
	private static LoginCrypt lc = null;
	private static LoginServerPacket lsp;

	public ServerToClientThread(Socket client)
	{
		clientSocket = client;
		lc = null;
	}

	@Override
	public void run()
	{
		try
		{
			toClient = new DataOutputStream(clientSocket.getOutputStream());
			serverSocket = new Socket("64.25.35.104", 2106);
			fromServer = new DataInputStream(serverSocket.getInputStream());
			LoginClientToServer thread = new LoginClientToServer(clientSocket, serverSocket);
			thread.start();
			lsp = new LoginServerPacket();
			// ----------------------------------------------------------

			while (serverSocket.isConnected() && !serverSocket.isClosed())
			{

				byte[] buff = new byte[2];
				int read = fromServer.read(buff);
				if (read < 0)
				{
					// this can happen on ipv6 stacks
					if (serverSocket.isConnected())
						serverSocket.close();
					if (clientSocket.isConnected())
						clientSocket.close();
					System.out.println("LoginServerClient: run(): Disconnected, unable to read data from connection");
					return;
				}
				int size = Sbyte2int(buff[0]) + Sbyte2int(buff[1]) * 256;
				byte[] data = new byte[size];
				fromServer.read(data, 2, size - 2);

				try
				{
					data[0] = buff[0];
					data[1] = buff[1];
				}
				catch (ArrayIndexOutOfBoundsException e)
				{
					System.out.println("LoginServerClient: run(): " + e.getLocalizedMessage());
					if (clientSocket.isConnected())
						clientSocket.close();
					return;
				}

				if (lc == null)
				{
					toClient.write(data);
					lc = new LoginCrypt();
					lc.decrypt(data, 2, data.length - 2);
					byte[] tmp = new byte[data.length - 2];
					System.arraycopy(data, 2, tmp, 0, data.length - 2);
					lc.decXORPass(tmp, 0, tmp.length);
					System.arraycopy(tmp, 0, data, 2, tmp.length);
					lsp.setBytes(data);
					lsp.readD();
					lsp.readD();
					lsp.readB(128);
					lsp.readD();
					lsp.readD();
					lsp.readD();
					lsp.readD();
					lc = new LoginCrypt(lsp.readB(16));
				}
				else
				{
					lc.decrypt(data, 2, data.length - 2);
					if (data[2] == 0x04)
						data = changeServerList(data);
					lc.crypt(data, 2, data.length - 2);
					toClient.write(data);
					toClient.flush();
				}
			}
		}
		catch (Exception e)
		{
			System.out.println("LoginServerClient: run(): " + e.getLocalizedMessage());
		}
		System.out.println("LoginServerClient: end");
	}

	private byte[] changeServerList(byte[] data)
	{
		lsp.setBytes(data);

		int serverCount = lsp.readC();
		int lastServer = lsp.readC(); // lastServer
		GameServerInfo[] gameServers = new GameServerInfo[serverCount];

		for (int i = 0; i < serverCount; i++)
		{
			gameServers[i] = new GameServerInfo();
			gameServers[i].id = lsp.readC();
			gameServers[i].ip = new StringBuilder().append(lsp.readC()).append(".").append(lsp.readC()).append(".").append(lsp.readC()).append(".").append(lsp.readC()).toString();
			gameServers[i].port = lsp.readD();
			gameServers[i].ageLimit = lsp.readC();
			gameServers[i].pvp = lsp.readC() != 0 ? true : false;
			gameServers[i].players = lsp.readH();
			gameServers[i].maxplayers = lsp.readH();
			gameServers[i].online = lsp.readC() != 0 ? true : false;
			gameServers[i].type = lsp.readD(); // 1: Normal, 2: Relax, 4: Public Test, 8: No Label, 16: Character Creation Restricted, 32: Event, 64: Free
			gameServers[i].brackets = lsp.readC() != 0 ? true : false;
		}
		int unk = lsp.readH(); // unknown
		// ------------------------------------------------------------------------------------------------
		byte[] newData = new byte[data.length];

		System.arraycopy(data, 0, newData, 0, data.length);
		lsp.setBytes(newData);
		lsp.writeC(serverCount);
		lsp.writeC(lastServer);
		for (GameServerInfo server : gameServers)
		{
			lsp.writeC(server.id);
			lsp.writeC(127);
			lsp.writeC(0);
			lsp.writeC(0);
			lsp.writeC(1);
			lsp.writeD(server.port);
			lsp.writeC(server.ageLimit);
			lsp.writeC(server.pvp == true ? 1 : 0);
			lsp.writeH(server.players);
			lsp.writeH(server.maxplayers);
			lsp.writeC(server.online == true ? 1 : 0);
			lsp.writeD(server.type);
			lsp.writeC(server.brackets == true ? 1 : 0);
		}
		lsp.writeH(unk);
		return lsp.getBytes();
	}

	private static String byteArrayToHexString(byte in[])
	{
		String rslt = "";
		String thes = "";
		for (int iuo = 0; iuo < in.length; iuo++)
		{
			int wtrf = in[iuo];
			if (wtrf < 0)
			{
				wtrf = 256 + wtrf;
			}
			// rslt += " " + (in[iuo]);

			thes = Integer.toHexString(wtrf);
			if (thes.length() < 2)
			{
				thes = "0" + thes;
			}
			rslt += " " + thes;
		}
		return rslt.toUpperCase();
	}

	private static int Sbyte2int(byte sb)
	{
		int wtrf = sb;
		if (wtrf < 0)
		{
			wtrf = 256 + wtrf;
		}
		return wtrf;
	}
}